"""
"""
import signal
from contextlib import closing
import json
import time
import requests
import logging
import hmac
import hashlib
import base64
import urllib.parse
# from logging import Logger
from loguru import logger
import smtplib
import os
from abc import ABC
from datetime import datetime
from time import sleep
from email.message import EmailMessage
from queue import Empty, Queue
from threading import Thread
from typing import Any, Sequence, Type, Dict, List, Optional

from vn_local.event import Event, EventEngine
from .app import BaseApp
from .event import (
    EVENT_TICK,
    EVENT_ORDER,
    EVENT_TRADE,
    EVENT_ORDERTRADE,
    EVENT_POSITION,
    EVENT_ACCOUNT,
    EVENT_CONTRACT,
    EVENT_LOG
)
from .gateway import BaseGateway
from .object import (
    CancelRequest,
    LogData,
    OrderRequest,
    SubscribeRequest,
    HistoryRequest,
    OrderData,
    BarData,
    TickData,
    TradeData,
    OrderTradeData,
    PositionData,
    AccountData,
    ContractData,
    Exchange,
    ReplaceRequest,
    CancelAllRequest, QueryOpenOrderRequest
)
from .setting import SETTINGS
from .utility import get_folder_path, TRADER_DIR
from .logger_util import VtLog, BbcLog, MainLog


class MainEngine:
    """
    Acts as the core of VN Trader.
    """

    def __init__(self, event_engine: EventEngine = None):
        """"""
        if event_engine:
            self.event_engine: EventEngine = event_engine
        else:
            self.event_engine = EventEngine()

        self.event_engine.start()

        self.gateways: Dict[str, BaseGateway] = {}
        self.engines: Dict[str, BaseEngine] = {}
        self.apps: Dict[str, BaseApp] = {}
        self.exchanges: List[Exchange] = []

        self.rpc_service = None

        os.chdir(TRADER_DIR)  # Change working directory
        self.init_engines()  # Initialize function engines

    def add_engine(self, engine_class: Any) -> "BaseEngine":
        """
        Add function engine.
        """
        engine = engine_class(self, self.event_engine)
        self.engines[engine.engine_name] = engine

        if engine.engine_name == 'RpcService':
            self.rpc_service = engine
        return engine

    def add_gateway(self, gateway_class: Type[BaseGateway]) -> BaseGateway:
        """
        Add gateway.
        """
        gateway = gateway_class(self.event_engine)
        # print('------------------------------------------------------------------------')
        # print(f'MainEngine | add_gateway | gateway: {gateway}')
        self.gateways[gateway.gateway_name] = gateway

        # Add gateway supported exchanges into engine
        for exchange in gateway.exchanges:
            if exchange not in self.exchanges:
                self.exchanges.append(exchange)

        return gateway

    def add_app(self, app_class: Type[BaseApp]) -> "BaseEngine":
        """
        Add app.
        """
        app = app_class()
        self.apps[app.app_name] = app
        engine = self.add_engine(app.engine_class)
        return engine

    def init_engines(self) -> None:
        """
        Init all engines.
        """
        self.add_engine(LogEngine)
        self.add_engine(OmsEngine)
        # self.add_engine(EmailEngine)

    def write_log(self, msg: str, source: str = "") -> None:
        """
        Put log event with specific message.
        """

        log = LogData(msg=msg, gateway_name=source)
        event = Event(EVENT_LOG, log)
        self.event_engine.put(event)
        # print("trader/engine/writelog")

    def get_gateway(self, gateway_name: str) -> BaseGateway:
        """
        Return gateway object by name.
        """
        gateway = self.gateways.get(gateway_name, None)
        if not gateway:
            self.write_log(f"找不到底层接口：{gateway_name}")
        return gateway

    def get_engine(self, engine_name: str) -> "BaseEngine":
        """
        Return engine object by name.
        """
        engine = self.engines.get(engine_name, None)
        if not engine:
            self.write_log(f"找不到引擎：{engine_name}")
        return engine

    def get_default_setting(self, gateway_name: str) -> Optional[Dict[str, Any]]:
        """
        Get default setting dict of a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.get_default_setting()
        return None

    def get_all_gateway_names(self) -> List[str]:
        """
        Get all names of gatewasy added in main engine.
        """
        return list(self.gateways.keys())

    def get_all_gateway_status(self) -> List[dict]:
        """
        Get all gateway status
        :return:
        """
        return list([{k: v.get_status()} for k, v in self.gateways.items()])

    def get_all_apps(self) -> List[BaseApp]:
        """
        Get all app objects.
        """
        return list(self.apps.values())

    def get_all_exchanges(self) -> List[Exchange]:
        """
        Get all exchanges.
        """
        return self.exchanges

    def connect(self, setting: dict, gateway_name: str) -> None:
        """
        Start connection of a specific gateway.
        """

        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.connect(setting)

    def subscribe(self, req: SubscribeRequest, gateway_name: str) -> None:
        """
        Subscribe tick data update of a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.subscribe(req)

    def send_order(self, req: OrderRequest, gateway_name: str) -> str:
        """
        Send new order request to a specific gateway.
        """
        # print(f"发单请求{req},{gateway_name}")
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.send_order(req)  # 再调用gateway，成功后返回的是id，不成功就false
        else:
            return ""

    def get_funding_rate(self, req, gateway_name: str) -> str:
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.get_funding_rate(req)
        else:
            return ""

    def cancel_order(self, req: CancelRequest, gateway_name: str) -> None:
        """
        Send cancel order request to a specific gateway.
        """
        # print(f"撤单请求{req},{gateway_name}")
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.cancel_order(req)  # 成功就是true，不成功就false

    def cancel_all_orders(self, req: CancelAllRequest, gateway_name: str) -> None:
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.cancel_all_orders(req)

    def query_open_orders(self, req: QueryOpenOrderRequest, gateway_name: str) -> None:
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.query_open_orders(req)

    def replace_order(self, req: ReplaceRequest, gateway_name: str) -> None:
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.replace_order(req)

    def query_order(self, req: CancelRequest, gateway_name: str) -> None:
        """
        查询委托是否成功
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.query_order(req)

    def send_orders(self, reqs: Sequence[OrderRequest], gateway_name: str) -> List[str]:
        """
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.send_orders(reqs)
        else:
            return ["" for req in reqs]

    def cancel_orders(self, reqs: Sequence[CancelRequest], gateway_name: str) -> None:
        """
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.cancel_orders(reqs)

    def query_history(self, req: HistoryRequest, gateway_name: str) -> Optional[List[BarData]]:
        """
        Send cancel order request to a specific gateway.
        """
        gateway = self.get_gateway(gateway_name)
        if gateway:
            return gateway.query_history(req)
        else:
            return None

    def query_position(self, gateway_name: str):
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.query_position()
        else:
            return None

    def query_contract(self, gateway_name: str):
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.query_contract()
        else:
            return None

    def query_account(self, gateway_name: str):
        gateway = self.get_gateway(gateway_name)
        if gateway:
            gateway.query_account()
        else:
            return None

    def close(self) -> None:
        """
        Make sure every gateway and app is closed properly before
        programme exit.
        """
        # Stop event engine first to prevent new timer event.
        self.event_engine.stop()

        # for engine in self.engines.values():
        #     engine.close()

        for gateway in self.gateways.values():
            gateway.close()

        pid = os.getpid()
        os.kill(pid, signal.SIGKILL)

    def send_dingding(self, msg: str):

        access_token = SETTINGS['dingding.access_token']
        if not access_token:
            self.write_log("还未配置钉钉机器人access_token")
            return
        secret = SETTINGS['dingding.secret']
        if not secret:
            self.write_log("还未配置钉钉机器人secret")
            return

        webhook = f"https://oapi.dingtalk.com/robot/send?access_token={access_token}"
        if secret:
            secret_enc = secret.encode('utf-8')
            timestamp = str(round(time.time() * 1000))
            string_to_sign = '{}\n{}'.format(timestamp, secret)
            string_to_sign_enc = string_to_sign.encode('utf-8')
            hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
            sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
            webhook += f"&timestamp={timestamp}&sign={sign}"
        header = {
            "Content-Type": "application/json",
            "Charset": "UTF-8"
        }
        message = {

            "msgtype": "text",
            "text": {
                "content": msg
            },
            # "at": {

            #     "isAtAll": True
            # }
        }
        message_json = json.dumps(message)
        try:
            info = requests.post(url=webhook, data=message_json, headers=header)
            resp = json.loads(info.text)
            if resp['errcode'] == 310000:
                self.write_log("钉钉机器人未配置加签或者加签的Key不对")
            elif resp['errcode'] == 130101:
                sleep(60)
                self.send_dingding(msg)
            elif resp['errcode'] != 0:
                print(resp)
                sleep(5)
                self.send_dingding(msg)
        except requests.exceptions.RequestException as e:
            print(e)
            self.send_dingding(msg)
        except Exception as e:
            print(e)
            self.write_log("发送钉钉出错")
        pass


class BaseEngine(ABC):
    """
    Abstract class for implementing an function engine.
    """

    def __init__(
            self,
            main_engine: MainEngine,
            event_engine: EventEngine,
            engine_name: str,
    ):
        """"""
        self.main_engine = main_engine
        self.event_engine = event_engine
        self.engine_name = engine_name

    def close(self):
        """"""
        pass
        # pid = os.getpid()
        # self.main_engine.write_log(f"关闭进程{pid}")
        # os.kill(pid, signal.SIGKILL)

    # def is_close_finished(self):
    #     pass
        # return False


class LogEngine(BaseEngine):
    """
    Processes log event and output with logging module.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super(LogEngine, self).__init__(main_engine, event_engine, "log")

        if not SETTINGS["log.active"]:
            return
        # logger.remove()
        # self.level: int = SETTINGS["log.level"]
        #
        # # 初始化多个日志记录器
        # # self.loggers = {
        # #     "vt": self.setup_logger("vt", "./.vntrader/log/vt_{time:YYYYMMDD_HHmm}.log"),
        # #     "hft": self.setup_logger("hft", "./.vntrader/log/hft_{time:YYYYMMDD_HHmm}.log"),
        # #     "main": self.setup_logger("main", "./.vntrader/log/main_{time:YYYYMMDD_HHmm}.log")
        # # }
        #
        # log_file_path = "./.vntrader/log/vt_{time:YYYYMMDD_HHmm}.log"
        #
        # LOG_NAME = "vt"
        # self.log = logger.bind(name=LOG_NAME, level=self.level)
        # logger.add(
        #     log_file_path,  # 使用分钟级时间戳作为文件名
        #     rotation="2 minutes",  # 每2分钟轮换一次日志文件
        #     format="{time:YYYY-MM-DD HH:mm:ss.SSS} {level}: {message}",  # 设置日志内容的时间格式为毫秒级
        #     enqueue=True
        # )

        self.register_event()

    def setup_logger(self, name: str, log_file_path: str):
        """
        Set up a logger with the given name and file path.
        """
        # 创建一个绑定名称的 logger
        custom_logger = logger.bind(name=name)

        # 配置 logger
        custom_logger.add(
            log_file_path,
            rotation="2 minutes",  # 每2分钟轮换一次日志文件
            format="{time:YYYY-MM-DD HH:mm:ss.SSS} {level}: {message}",  # 设置日志内容格式，包含日志名称
            level=self.level,  # 设置日志级别
            enqueue=True,  # 适用于多线程环境
            backtrace=True,  # 记录详细的错误回溯信息
            diagnose=True,  # 提供额外的诊断信息
        )

        return custom_logger

    def register_event(self) -> None:
        """"""
        self.event_engine.register(EVENT_LOG, self.process_log_event)

    def process_log_event(self, event: Event) -> None:
        """
        Process log event and route to the correct logger.
        """
        log = event.data
        # print(log)
        if log.prefix == "vt":
            # print(VtLog)
            VtLog.info(log.msg)
        elif log.prefix == "bbc":
            BbcLog.info(log.msg)
        else:
            MainLog.info(log.msg)

        # prefix = log.prefix
        # custom_logger = self.loggers.get(prefix)
        #
        # # 输出日志消息
        # if log.level == 20:
        #     custom_logger.info(log.msg)
        # elif log.level == 40:
        #     custom_logger.error(log.msg)


class OmsEngine(BaseEngine):
    """
    Provides order management system function for VN Trader.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super(OmsEngine, self).__init__(main_engine, event_engine, "oms")

        self.ticks: Dict[str, TickData] = {}
        self.orders: Dict[str, OrderData] = {}
        self.order_trades: Dict[str, OrderTradeData] = {}
        self.trades: Dict[str, TradeData] = {}
        self.positions: Dict[str, PositionData] = {}
        self.accounts: Dict[str, AccountData] = {}
        self.contracts: Dict[str, ContractData] = {}

        self.active_orders: Dict[str, OrderData] = {}
        self.active_order_trades: Dict[str, OrderTradeData] = {}
        self.add_function()
        self.register_event()

    def add_function(self) -> None:
        """Add query function to main engine."""
        self.main_engine.get_tick = self.get_tick
        self.main_engine.get_order = self.get_order
        self.main_engine.get_trade = self.get_trade
        self.main_engine.get_order_trade = self.get_order_trade
        self.main_engine.get_position = self.get_position
        self.main_engine.get_account = self.get_account
        self.main_engine.get_contract = self.get_contract
        self.main_engine.get_all_ticks = self.get_all_ticks
        self.main_engine.get_all_orders = self.get_all_orders
        self.main_engine.get_all_trades = self.get_all_trades
        self.main_engine.get_all_positions = self.get_all_positions
        self.main_engine.get_all_accounts = self.get_all_accounts
        self.main_engine.get_all_contracts = self.get_all_contracts
        self.main_engine.get_all_active_orders = self.get_all_active_orders

    def register_event(self) -> None:
        """"""
        self.event_engine.register(EVENT_TICK, self.process_tick_event)
        self.event_engine.register(EVENT_ORDER, self.process_order_event)
        self.event_engine.register(EVENT_TRADE, self.process_trade_event)
        self.event_engine.register(EVENT_ORDERTRADE, self.process_order_trade_event)
        self.event_engine.register(EVENT_POSITION, self.process_position_event)
        self.event_engine.register(EVENT_ACCOUNT, self.process_account_event)
        self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)

    def process_tick_event(self, event: Event) -> None:
        """"""
        tick: TickData = event.data
        self.ticks[tick.vt_symbol] = tick

    def process_order_event(self, event: Event) -> None:
        """"""

        # 这里要看清楚是怎么调用的

        order: OrderData = event.data
        self.orders[order.vt_orderid] = order

        # If order is active, then update data in dict.
        if order.is_active():
            self.active_orders[order.vt_orderid] = order
        # Otherwise, pop inactive order from in dict
        elif order.vt_orderid in self.active_orders:
            self.active_orders.pop(order.vt_orderid)

        if len(self.orders) > 10000:
            self.orders.pop(list(self.orders)[0])

    def process_order_trade_event(self, event: Event) -> None:
        """"""

        # 这里要看清楚是怎么调用的

        order_trade: OrderTradeData = event.data
        self.order_trades[order_trade.vt_orderid] = order_trade

        # If order is active, then update data in dict.
        if order_trade.is_active():
            self.active_order_trades[order_trade.vt_orderid] = order_trade
        # Otherwise, pop inactive order from in dict
        elif order_trade.vt_orderid in self.active_orders:
            self.active_order_trades.pop(order_trade.vt_orderid)

        if len(self.order_trades) > 10000:
            self.order_trades.pop(list(self.order_trades)[0])

    def process_trade_event(self, event: Event) -> None:
        """"""
        trade: TradeData = event.data
        self.trades[trade.vt_tradeid] = trade

        if len(self.trades) > 10000:
            self.trades.pop(list(self.orders)[0])

    def process_position_event(self, event: Event) -> None:
        """"""
        position: PositionData = event.data
        self.positions[position.vt_positionid] = position
        # print('main_engine', position)

    def process_account_event(self, event: Event) -> None:
        """"""
        account: AccountData = event.data
        self.accounts[account.exchange.value] = account
        # print(self.accounts)

    def process_contract_event(self, event: Event) -> None:
        """"""
        contract: ContractData = event.data
        self.contracts[contract.vt_symbol] = contract
        # print("engine", self.contracts)

    def get_tick(self, vt_symbol: str) -> Optional[TickData]:
        """
        Get latest market tick data by vt_symbol.
        """
        return self.ticks.get(vt_symbol, None)

    def get_order(self, vt_orderid: str) -> Optional[OrderData]:
        """
        Get latest order data by vt_orderid.
        """
        return self.orders.get(vt_orderid, None)

    def get_order_trade(self, vt_orderid: str) -> Optional[OrderTradeData]:
        return self.order_trades.get(vt_orderid, None)

    def get_trade(self, vt_tradeid: str) -> Optional[TradeData]:
        """
        Get trade data by vt_tradeid.
        """
        return self.trades.get(vt_tradeid, None)

    def get_position(self, vt_positionid: str) -> Optional[PositionData]:
        """
        Get latest position data by vt_positionid.
        """
        return self.positions.get(vt_positionid, None)

    def get_account(self, vt_accountid: str) -> Optional[AccountData]:
        """
        Get latest account data by vt_accountid.
        """
        return self.accounts.get(vt_accountid, None)

    def get_contract(self, vt_symbol: str) -> Optional[ContractData]:
        """
        Get contract data by vt_symbol.
        """
        return self.contracts.get(vt_symbol, None)

    def get_all_ticks(self) -> List[TickData]:
        """
        Get all tick data.
        """
        return list(self.ticks.values())

    def get_all_orders(self) -> List[OrderData]:
        """
        Get all order data.
        """
        return list(self.orders.values())

    def get_all_order_trades(self) -> List[OrderTradeData]:
        return list(self.order_trades.values())

    def get_all_trades(self) -> List[TradeData]:
        """
        Get all trade data.
        """
        return list(self.trades.values())

    def get_all_positions(self) -> List[PositionData]:
        """
        Get all position data.
        """
        return list(self.positions.values())

    def get_all_accounts(self) -> List[AccountData]:
        """
        Get all account data.
        """
        return list(self.accounts.values())

    def get_all_contracts(self) -> List[ContractData]:
        """
        Get all contract data.
        """
        return list(self.contracts.values())

    def get_all_active_orders(self, vt_symbol: str = "") -> List[OrderData]:
        """
        Get all active orders by vt_symbol.

        If vt_symbol is empty, return all active orders.
        """
        if not vt_symbol:
            return list(self.active_orders.values())
        else:
            active_orders = [
                order
                for order in self.active_orders.values()
                if order.vt_symbol == vt_symbol
            ]
            return active_orders


class EmailEngine(BaseEngine):
    """
    Provides email sending function for VN Trader.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super(EmailEngine, self).__init__(main_engine, event_engine, "email")

        self.thread: Thread = Thread(target=self.run)
        self.queue: Queue = Queue()
        self.active: bool = False

        self.main_engine.send_email = self.send_email

    def send_email(self, subject: str, content: str, receiver: str = "") -> None:
        """"""
        # Start email engine when sending first email.
        if not self.active:
            self.start()

        # Use default receiver if not specified.
        if not receiver:
            receiver = SETTINGS["email.receiver"]

        msg = EmailMessage()
        msg["From"] = SETTINGS["email.sender"]
        msg["To"] = receiver
        msg["Subject"] = subject
        msg.set_content(content)

        self.queue.put(msg)

    def run(self) -> None:
        """"""
        while self.active:
            try:
                msg = self.queue.get(block=True, timeout=1)

                with smtplib.SMTP_SSL(
                        SETTINGS["email.server"], SETTINGS["email.port"]
                ) as smtp:
                    smtp.login(
                        SETTINGS["email.username"], SETTINGS["email.password"]
                    )
                    smtp.send_message(msg)
            except (
                    smtplib.SMTPConnectError,
                    smtplib.SMTPHeloError,
                    smtplib.SMTPAuthenticationError,
                    smtplib.SMTPServerDisconnected
            ):
                print("连接SMTP出错")
                sleep(5)
                self.queue.put(msg)
            except Empty:
                pass

    def start(self) -> None:
        """"""
        self.active = True
        self.thread.start()

    def close(self) -> None:
        """"""
        if not self.active:
            return

        self.active = False
        self.thread.join()
